#!/usr/bin/env python3

from __future__ import annotations

import sys
import tempfile
import urllib.request
import pathlib

import apsw
import apsw.ext
import apsw.shell

title_to_mapping = {
    "Allowed return values from sqlite3_txn_state()": "txn_state",
    "Authorizer Action Codes": "authorizer_function",
    "Authorizer Return Codes": "authorizer_return_codes",
    "Checkpoint Mode Values": "wal_checkpoint",
    "Compile-Time Library Version Numbers": None,
    "Configuration Options": "config",
    "Conflict resolution modes": "conflict_resolution_modes",
    "Constants Defining Special Destructor Behavior": None,
    "Database Connection Configuration Options": "db_config",
    "Datatypes for the CARRAY table-valued function": "carray",
    "Device Characteristics": "device_characteristics",
    "Extended Result Codes": "extended_result_codes",
    "File Locking Levels": "locking_level",
    "Flags For File Open Operations": "open_flags",
    "Flags for sqlite3_deserialize()": None,
    "Flags for sqlite3_serialize": None,
    "Flags for sqlite3_setlk_timeout()": "setlk_timeout_flags",
    "Flags for the xAccess VFS method": "access",
    "Flags for the xShmLock VFS method": "xshmlock_flags",
    "Function Flags": "function_flags",
    "Fundamental Datatypes": None,
    "Maximum xShmLock index": None,
    "Mutex Types": None,
    "Prepare Flags": "prepare_flags",
    "Prepared Statement Scan Status": None,
    "Prepared Statement Scan Status Opcodes": None,
    "Result Codes": "result_codes",
    "Run-Time Limit Categories": "limits",
    "SQL Trace Event Codes": "trace_codes",
    "Standard File Control Opcodes": "file_control",
    "Status Parameters": "status",
    "Status Parameters for database connections": "db_status",
    "Status Parameters for prepared statements": "statement_status",
    "Synchronization Type Flags": "sync",
    "Testing Interface Operation Codes": None,
    "Text Encodings": None,
    "Virtual Table Configuration Options": "virtual_table_configuration_options",
    "Virtual Table Constraint Operator Codes": "bestindex_constraints",
    "Virtual Table Scan Flags": "virtual_table_scan_flags",
    "Win32 Directory Types": None,
    "FTS5 Token Flag": "fts5_token_flags",
    "FTS5 Tokenize Reason": "fts5_tokenize_reason",
    "Flags for sqlite3changeset_apply_v2": "session_changeset_apply_v2_flags",
    "Flags for sqlite3changeset_start_v2": "session_changeset_start_v2_flags",
    "Constants Returned By The Conflict Handler": "session_conflict_response",
    "Constants Passed To The Conflict Handler": "session_conflict",
    "Values for sqlite3session_config": "session_config_options",
    "Options for sqlite3session_object_config": "session_object_config_options",
}

base_sqlite_url = "https://sqlite.org/"
with tempfile.NamedTemporaryFile() as f:
    f.write(urllib.request.urlopen(base_sqlite_url + "toc.db").read())
    f.flush()

    db = apsw.Connection(f.name)
    db.execute(pathlib.Path(__file__).with_name("tocupdate.sql").read_text())
    db.row_trace = apsw.ext.DataClassRowFactory(dataclass_kwargs={"frozen": True})

    # key is English title, value is the list of constants for that
    constants: dict[str, list[str]] = {}

    cur_mapping_title = None

    for row in db.execute("select * from toc where type='constant' order by title, name"):
        if row.title != cur_mapping_title:
            constants[row.title] = []
            cur_mapping_title = row.title
        assert row.name not in constants[row.title]
        constants[row.title].append(row.name)

unknown = {title for title in constants if title not in title_to_mapping}
if unknown:
    sys.exit(f"Unknown title mapping { unknown }")

header = """\
/*
    Generated by genconstants.py from SQLite's toc.db
    with updates from tools/tocupdate.sql

    Deal with those - do not edit this file
*/

/* returns zero on success, -1 on error */
static int
add_apsw_constants(PyObject *module)
{
    PyObject *the_dict;

    assert(!PyErr_Occurred());
"""
trailer = """
    assert(!PyErr_Occurred());
    return 0;
}
"""

per_item = """\
    if (!the_dict)
    {
        assert(PyErr_Occurred());
        return -1;
    }
    if (PyModule_AddObject(module, "mapping_NAME", the_dict))
    {
        assert(PyErr_Occurred());
        Py_DECREF(the_dict);
        return -1;
    }"""

def get_ifdef(name):
    for title, values in constants.items():
        if name in values:
            mapping = title_to_mapping[title]
            if mapping:
                if mapping.startswith("session_") and name in constants[title]:
                    return "SQLITE_ENABLE_SESSION"
                if mapping == "carray":
                    return "SQLITE_ENABLE_CARRAY"

    return None

def constant_sort_key(name):
    return (get_ifdef(name) or "", name)


op: list[str] = [header]

top_level: set[str] = set()

for title, cons in sorted(constants.items()):
    if not title_to_mapping[title]:
        continue

    ifdef = get_ifdef(cons[-1])

    if ifdef:
        op.append(f"#ifdef {ifdef}")
    op.append(f"    /* { title } */")
    op.append("    the_dict = Py_BuildValue(")
    op.append('        "{' + "siis" * len(cons) + '}",')
    for c in cons:
        top_level.add(c)
        op.append(f'        "{ c }", { c }, { c }, "{ c }",')
    op[-1] = op[-1].rstrip(",") + ");"
    op.append(per_item.replace("NAME", title_to_mapping[title]))
    if ifdef:
        op.append(f"#endif /* {ifdef} */")
    op.append("")


op.append("    if (")
last_ifdef = None
for i, c in enumerate(sorted(top_level, key=constant_sort_key)):
    ifdef = get_ifdef(c)
    if ifdef != last_ifdef:
        if last_ifdef:
            op.append(f"#endif /* {last_ifdef} */")
        if ifdef:
            op.append(f"#ifdef {ifdef}")
        last_ifdef = ifdef
    sor = "|| " if i else ""
    op.append(f'        { sor }PyModule_AddIntConstant(module, "{ c }", { c })')
op.append(f"#endif /* {last_ifdef} */")
op.append("    )")

op.append("        return -1;")

op.append(trailer)

print("\n".join(op))
